/*******************************************************************************
* Copyright (c) 2016, 2017 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.tests.progress;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.operation.ProgressMonitorUtil;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.tests.harness.util.UITestCase;
import org.junit.Test;
/**
* Test the accumulating progress monitor wrapper api, which fires received
* progress events to it's wrapped monitor in the UI thread, and makes efficient
* use of a collector, to minimize the number of unnecessary events propagated
* on the UI thread.
*
* @since 3.5
*
*/
public class AccumulatingProgressMonitorTest extends UITestCase {
/**
* @param testName
*/
public AccumulatingProgressMonitorTest(String testName) {
super(testName);
}
/*
* A monitor to be wrapped. This monitor's methods assert that each method
* call is in the correct thread. Most will be on the UI thread but some
* methods are exempt.
*/
private class UIThreadAsserterMonitor implements IProgressMonitorWithBlocking {
public boolean beginTaskCalled = false;
public boolean setTaskNameCalled = false;
public boolean subTaskCalled = false;
public boolean setBlockedCalled = false;
public boolean clearBlockedCalled = false;
public boolean workedCalled = false;
public boolean internalWorkedCalled = false;
public boolean doneCalled = false;
public boolean isCanceledCalled = false;
public boolean setCanceledCalled = false;
@Override
public void beginTask(String name, int totalWork) {
beginTaskCalled = true;
assertTrue(Display.getDefault().getThread() == Thread.currentThread());
}
@Override
public void done() {
doneCalled = true;
assertTrue(Display.getDefault().getThread() == Thread.currentThread());
}
@Override
public void internalWorked(double work) {
internalWorkedCalled = true;
assertTrue(Display.getDefault().getThread() == Thread.currentThread());
}
@Override
public boolean isCanceled() {
isCanceledCalled = true;
assertFalse(Display.getDefault().getThread() == Thread.currentThread());
return false;
}
@Override
public void setCanceled(boolean value) {
setCanceledCalled = true;
assertFalse(Display.getDefault().getThread() == Thread.currentThread());
}
@Override
public void setTaskName(String name) {
setTaskNameCalled = true;
assertTrue(Display.getDefault().getThread() == Thread.currentThread());
}
@Override
public void subTask(String name) {
subTaskCalled = true;
assertTrue(Display.getDefault().getThread() == Thread.currentThread());
}
@Override
public void worked(int work) {
workedCalled = true;
assertTrue(Display.getDefault().getThread() == Thread.currentThread());
}
@Override
public void setBlocked(IStatus reason) {
setBlockedCalled = true;
assertTrue(Display.getDefault().getThread() == Thread.currentThread());
}
@Override
public void clearBlocked() {
clearBlockedCalled = true;
assertTrue(Display.getDefault().getThread() == Thread.currentThread());
}
}
/*
* A monitor to be wrapped by an AccumulatingProgressMonitor This monitor
* will keep a reference to every setTaskName call and be able to provide
* the list later for inspection.
*/
private class CollectorAsserterMonitor implements IProgressMonitorWithBlocking {
ArrayList<String> receivedTaskNames = new ArrayList<>();
public ArrayList<String> getTaskNames() {
return receivedTaskNames;
}
@Override
public void beginTask(String name, int totalWork) {
}
@Override
public void done() {
}
@Override
public void internalWorked(double work) {
}
@Override
public boolean isCanceled() {
return false;
}
@Override
public void setCanceled(boolean value) {
}
@Override
public void setTaskName(String name) {
receivedTaskNames.add(name);
}
@Override
public void subTask(String name) {
}
@Override
public void worked(int work) {
}
@Override
public void setBlocked(IStatus reason) {
}
@Override
public void clearBlocked() {
}
}
/**
* Call all applicable methods in a separate thread. Since the test runs in
* the UI thread, we must fire a new thread to do the initial calls. While
* the thread is running, we will also run the event loop until either the
* thread dies or a maximum duration has passed. Then, assert that no
* assertion exceptions were thrown during the calls to
* UIThreadAsserterMonitor's methods, and assert that each method was in
* fact called.
*/
@Test
public void testAccumulatingMonitorInUIThread() throws Exception {
Semaphore uiSemaphore = new Semaphore(0);
Semaphore backgroundSemaphore = new Semaphore(0);
final Throwable[] death = new Throwable[1];
final UIThreadAsserterMonitor[] mon2 = new UIThreadAsserterMonitor[1];
Thread t = new Thread("Test Accumulating Monitor") {
@Override
public void run() {
int uiReleaseCount = 0;
try {
UIThreadAsserterMonitor tm = new UIThreadAsserterMonitor();
mon2[0] = tm;
IProgressMonitorWithBlocking wrapper = ProgressMonitorUtil.createAccumulatingProgressMonitor(tm,
Display.getDefault());
wrapper.beginTask("Some Task", 100);
wrapper.setTaskName("Task Name");
wrapper.subTask("Subtask");
// call work, but internalWorked will be called
// on our monitor instead
wrapper.worked(10);
wrapper.isCanceled();
wrapper.setCanceled(false);
wrapper.setBlocked(new Status(IStatus.ERROR, "org.eclipse.ui.tests", "Some Error"));
uiSemaphore.release();
uiReleaseCount++;
backgroundSemaphore.acquire();
wrapper.clearBlocked();
wrapper.done();
uiSemaphore.release();
uiReleaseCount++;
} catch (Throwable t) {
death[0] = t;
if (uiReleaseCount == 0) {
// we never released UI, test is frozen
uiReleaseCount++;
uiSemaphore.release();
try {
backgroundSemaphore.acquire();
} catch (InterruptedException ie) {
}
}
// We only released UI once, it is still waiting for 2nd
// release
if (uiReleaseCount == 1) {
uiSemaphore.release();
}
}
}
};
t.start();
uiSemaphore.acquire();
runEventLoopUntilEmpty();
backgroundSemaphore.release();
uiSemaphore.acquire();
runEventLoopUntilEmpty();
assertNull("Wrapped monitor not executed in UI thread", death[0]);
assertNotNull(mon2[0]);
assertTrue(mon2[0].beginTaskCalled);
assertTrue(mon2[0].setTaskNameCalled);
assertTrue(mon2[0].subTaskCalled);
// AccumulatingProgressMonitor calls internalWorked instead
assertFalse(mon2[0].workedCalled);
assertTrue(mon2[0].internalWorkedCalled);
assertTrue(mon2[0].isCanceledCalled);
assertTrue(mon2[0].setCanceledCalled);
assertTrue(mon2[0].setBlockedCalled);
assertTrue(mon2[0].clearBlockedCalled);
assertTrue(mon2[0].doneCalled);
}
/**
* Call setTaskName a large number of times and verify a sufficiently
* efficient lower number were actually propagated to our monitor. This
* verifies that the UI thread is not firing all events, and the
* AccumulatingProgressMonitor's collector is functioning properly.
*/
@Test
public void testCollector() {
final CollectorAsserterMonitor mon = new CollectorAsserterMonitor();
final int[] numLoops = new int[] { 10000 };
IProgressMonitorWithBlocking wrapper = ProgressMonitorUtil.createAccumulatingProgressMonitor(mon,
Display.getDefault());
wrapper.beginTask("Some Task", 100);
for (int i = 0; i < numLoops[0]; i++) {
wrapper.setTaskName("Task Name " + i);
}
wrapper.done();
ArrayList<String> tasks = mon.getTaskNames();
int size = tasks.size();
assertEquals(0, size); // No events should have been processed yet
runEventLoopUntilEmpty();
tasks = mon.getTaskNames();
size = tasks.size();
assertEquals(1, size);
String expected = "Task Name " + (numLoops[0] - 1);
assertEquals(expected, tasks.get(0));
}
/*
* Wait for the given thread to complete or a maximum of 'ms' milliseconds
*/
private void runEventLoopUntilEmpty() {
Display display = Display.getCurrent();
while (display.readAndDispatch()) {
}
return;
}
}